package com.reger.l2cache.serverid.config;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.Assert;

import com.reger.l2cache.serverid.ServerId;
import com.reger.l2cache.serverid.event.ServerIdRefreshApplicationEvent;

@Configuration
@EnableScheduling
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnMissingBean(ServerIdAutoConfiguration.class)
public class ServerIdAutoConfiguration implements ApplicationContextAware {

	private static final Logger LOGGER = LoggerFactory.getLogger(ServerIdAutoConfiguration.class);
	private StringRedisTemplate stringRedisTemplate;
	private ApplicationContext applicationContext;
	private Integer serverId = -1;
	private static boolean settingsEd = true;
	private final static long TIME_OUT_SECONDS = 30 * 60;
	private final static long SCHEDULED_DELAY = TIME_OUT_SECONDS * 50;
	private final static String serverIdStr = "{0}server_id_hash";

	private final static String lua = ("local serverIdHash='" + serverIdStr + "';"
			+ "local serverIds=redis.call('HGETALL', serverIdHash);" + "local localServerId=nil;"
			+ "for serverId = 0,2048 do " + "    if (redis.call('HEXISTS', serverIdHash ,serverId)==0) then "
			+ "      localServerId = serverId;" + "      break;" + "    end;"
			+ "    if(redis.call('EXISTS',  serverIdHash..':'..serverId)==0) then" + "      localServerId = serverId;"
			+ "      break;" + "    end;" + "end;" + "local valKey=serverIdHash..':'..localServerId;"
			+ "redis.call('HSET', serverIdHash, localServerId..'', valKey);" + "redis.call('SETEX', valKey, "
			+ TIME_OUT_SECONDS + ", localServerId);" + "return localServerId ;");

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
		this.stringRedisTemplate = applicationContext.getBean(StringRedisTemplate.class);
		if (!settingsEd) {
			return;
		}
		settingsEd = false;
		this.select();
	}

	private String getServiceKey(int serverid) {
		return serverIdStr + ":" + serverid;
	}

	@Scheduled(initialDelay = SCHEDULED_DELAY, fixedDelay = SCHEDULED_DELAY)
	protected void scheduledServerid() {
		String serviceKey = getServiceKey(serverId);
		if (stringRedisTemplate.hasKey(serviceKey)) {
			LOGGER.info("服务器id {} 续订未超时，续订半小时 ", serverId);
			stringRedisTemplate.expire(serviceKey, TIME_OUT_SECONDS, TimeUnit.SECONDS);
			LOGGER.debug("服务器ID {} 续订完毕", serverId);
		} else {
			LOGGER.warn("服务器id {} 续订超时  重新获取 ", serverId);
			this.select();
			LOGGER.warn("重新获取服务器ID {} 完毕", serverId);
		}
	}

	private void select() {
		Long serverId = stringRedisTemplate.execute((RedisConnection connection) -> connection.eval(lua.getBytes(),
				ReturnType.INTEGER, 1, "{0}".getBytes()));
		Assert.notNull(serverId, "获取服务器id失败");
		LOGGER.debug("获取服务器ID成功为{}", serverId);
		Assert.isTrue(serverId < 2048, "serverid超过允许的最大值");
		this.serverId = serverId.intValue();
		ServerId.set(this.serverId);
		applicationContext.publishEvent(new ServerIdRefreshApplicationEvent(this, this.serverId));
	}

}
